Skip to content

perf(SegmentedControl): replace :has(+ [data-selected]) divider rule with adjacent-sibling selector#7903

Draft
mattcosta7 wants to merge 2 commits into
mainfrom
perf/segmentedcontrol-divider-no-has
Draft

perf(SegmentedControl): replace :has(+ [data-selected]) divider rule with adjacent-sibling selector#7903
mattcosta7 wants to merge 2 commits into
mainfrom
perf/segmentedcontrol-divider-no-has

Conversation

@mattcosta7
Copy link
Copy Markdown
Contributor

Closes #

Replaces the &:has(+ [data-selected])::after selector that hid the inter-item divider sitting just before the selected item. Previously the engine had to re-test a sibling-direction :has() against every .Item on every selection change.

Approach: flip the divider's side.

Drawing the divider on the leading edge of each non-first item (rather than the trailing edge of each non-last item) makes "the divider next to the selected item" expressible as adjacent-sibling combinators only:

/* Before */
.Item:not(:last-child)::after { /* divider */ }
.Item:has(+ [data-selected])::after,        /* item BEFORE the selected one */
.Item[data-selected]::after { background-color: transparent; }

/* After */
.Item:not(:first-child)::before { /* divider */ }
.Item[data-selected]::before,               /* selected item itself */
.Item[data-selected] + .Item::before { background-color: transparent; } /* item AFTER the selected one */

The same 1px line lives in the same gap between any two adjacent items — visually identical. The hide rules become two plain combinator selectors that the engine resolves in constant time per-element on a selection change, with no :has() walk.

The companion .Button:focus:focus-visible:not(:last-child)::after rule a few lines below is a pre-existing no-op on .Button::after (which has no content) and is left alone; it never targeted .Item::after.

Changelog

Changed

  • Internal: SegmentedControl inter-item divider is drawn on .Item::before of each non-first item instead of .Item::after of each non-last item.

Removed

  • The &:has(+ [data-selected])::after selector from SegmentedControl.module.css.

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Testing & Reviewing

  • All 19 packages/react/src/SegmentedControl/** unit tests pass.
  • tsc --noEmit on packages/react is clean.
  • Stylelint + Prettier + ESLint clean on touched file.

VRT expectations: None. The divider's geometry is unchanged — same 1px line at the same horizontal position, just attached to the item on its right rather than the item on its left. Hide-on-selection behavior is bit-identical (selected item and its trailing neighbour both lose their leading divider, mirroring the previous "item before selected + selected both lose their trailing divider").

Part of the :has()-reduction series: see also #7901 (PageHeader) and #7902 (ActionList SubGroup active indicator).

Merge checklist

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 29, 2026

🦋 Changeset detected

Latest commit: 83d8e52

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label May 29, 2026
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Action required

👋 Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the integration workflow. Check the integration testing docs for step-by-step instructions. Or, apply the integration-tests: skipped manually label to skip these checks.

To publish a canary release for integration testing, apply the Canary Release label to this PR.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves SegmentedControl CSS selector performance by removing a sibling-direction :has() selector used to hide the inter-item divider adjacent to the selected item, replacing it with constant-time adjacent-sibling combinators while keeping the divider’s visual position and behavior unchanged.

Changes:

  • Move the inter-item divider from .Item::after (non-last items) to .Item::before (non-first items).
  • Replace the :has(+ [data-selected])-based “hide divider next to selection” rule with .Item[data-selected] + .Item::before plus a selected-item rule.
  • Add a patch changeset documenting the internal CSS change and rationale.
Show a summary per file
File Description
packages/react/src/SegmentedControl/SegmentedControl.module.css Reworks divider rendering to avoid :has(+ …) and uses adjacent-sibling selectors to hide dividers near the selected item.
.changeset/perf-segmentedcontrol-divider-no-has.md Adds a patch changeset describing the internal divider selector change and expected no-visual-diff outcome.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants